package vm

import (
	"compiler/src/code"
	"compiler/src/compiler"
	"compiler/src/object"
	"fmt"
)

const StackSize = 2048

type VM struct {
	constants    []object.Object
	instructions code.Instructions

	stack []object.Object
	sp    int
}

func New(bytecode *compiler.Bytecode) *VM {
	return &VM{
		instructions: bytecode.Instructions,
		constants:    bytecode.Constants,

		stack: make([]object.Object, StackSize),
		sp:    0,
	}
}

func (vm *VM) StackTop() object.Object {
	if vm.sp == 0 {
		return nil
	}
	return vm.stack[vm.sp-1]
}

func (vm *VM) LastPoppedStackElem() object.Object {
	return vm.stack[vm.sp]
}

func (vm *VM) Run() error {
	for ip := 0; ip < len(vm.instructions); ip++ {
		op := code.OpCode(vm.instructions[ip])

		switch op {
		case code.OpBang:
			err := vm.executePrefix(op)
			if err != nil {
				return err
			}
		case code.OpConstant:
			constIndex := code.ReadUint16(vm.instructions[ip+1:])
			ip += 2
			err := vm.push(vm.constants[constIndex])
			if err != nil {
				return err
			}
		case code.OpAdd, code.OpMinus, code.OpAsterisk, code.OpSlash:
			err := vm.executeBinaryOperation(op)
			if err != nil {
				return nil
			}
		case code.OpGe, code.OpGt, code.OpEqual, code.OpNotEqual, code.OpLt, code.OpLe:
			err := vm.executeComparison(op)
			if err != nil {
				return nil
			}
		case code.OpTrue:
			vm.push(&object.Boolean{Value: true})
		case code.OpFalse:
			vm.push(&object.Boolean{Value: false})
		case code.OpPop:
			vm.pop()
		}
	}

	return nil
}

func (vm *VM) push(o object.Object) error {
	if vm.sp >= StackSize {
		return fmt.Errorf("stack overflow")
	}

	vm.stack[vm.sp] = o
	vm.sp++
	return nil
}

func (vm *VM) pop() object.Object {
	o := vm.stack[vm.sp-1]
	vm.sp--
	return o
}

func (vm *VM) executeBinaryOperation(op code.OpCode) error {
	right := vm.pop()
	left := vm.pop()

	rightType := right.Type()
	leftType := left.Type()

	if leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ {
		return vm.executeBinaryIntegerOperation(op, left, right)
	}

	return fmt.Errorf("unsupported types for binary operation: %s %s", leftType, rightType)
}

func (vm *VM) executeBinaryIntegerOperation(op code.OpCode, left, right object.Object) error {
	rightValue := right.(*object.Integer).Value
	leftValue := left.(*object.Integer).Value
	var result int64
	switch op {
	case code.OpAdd:
		result = leftValue + rightValue
	case code.OpMinus:
		result = leftValue - rightValue
	case code.OpAsterisk:
		result = leftValue * rightValue
	case code.OpSlash:
		result = leftValue / rightValue
	default:
		return fmt.Errorf("unknown integer operator: %d", op)
	}
	return vm.push(&object.Integer{Value: result})
}

func (vm *VM) executeComparison(op code.OpCode) error {
	right := vm.pop()
	left := vm.pop()
	rightType := right.Type()
	leftType := left.Type()

	if rightType == object.INTEGER_OBJ && leftType == object.INTEGER_OBJ {
		return vm.executeIntegerComparison(op, left, right)
	} else if rightType == object.BOOLEAN_OBJ && leftType == object.BOOLEAN_OBJ {
		return vm.executeBoolComparison(op, left, right)
	}

	return fmt.Errorf("unsupported types for bool operation: %s %s", leftType, rightType)
}

func (vm *VM) executeIntegerComparison(op code.OpCode, left object.Object, right object.Object) error {
	leftValue := left.(*object.Integer).Value
	rightValue := right.(*object.Integer).Value
	var result bool

	switch op {
	case code.OpEqual:
		if leftValue == rightValue {
			result = true
		} else {
			result = false
		}
	case code.OpNotEqual:
		if leftValue != rightValue {
			result = true
		} else {
			result = false
		}
	case code.OpGt:
		if leftValue > rightValue {
			result = true
		} else {
			result = false
		}
	case code.OpGe:
		if leftValue >= rightValue {
			result = true
		} else {
			result = false
		}
	case code.OpLt:
		if leftValue < rightValue {
			result = true
		} else {
			result = false
		}
	case code.OpLe:
		if leftValue <= rightValue {
			result = true
		} else {
			result = false
		}
	default:
		return fmt.Errorf("unknown bool operation: %d", op)
	}
	return vm.push(&object.Boolean{Value: result})
}

func (vm *VM) executeBoolComparison(op code.OpCode, left object.Object, right object.Object) error {
	leftValue := left.(*object.Boolean).Value
	rightValue := right.(*object.Boolean).Value
	var result bool

	switch op {
	case code.OpEqual:
		if leftValue == rightValue {
			result = true
		} else {
			result = false
		}
	case code.OpNotEqual:
		if leftValue != rightValue {
			result = true
		} else {
			result = false
		}
	default:
		return fmt.Errorf("unknown bool operation: %d", op)
	}
	return vm.push(&object.Boolean{Value: result})
}

func (vm *VM) executePrefix(op code.OpCode) error {
	var result bool
	switch op {
	case code.OpBang:
		right := vm.pop()
		rightType := right.Type()
		if rightType == object.BOOLEAN_OBJ {
			rightValue := right.(*object.Boolean).Value
			if rightValue {
				result = false
			} else {
				result = true
			}
		} else {
			return fmt.Errorf("unknown prefix operation type: %T(%+v)", right, right)
		}
	default:
		return fmt.Errorf("unknown prefix operation: %T(%+v)", op, op)
	}
	return vm.push(&object.Boolean{Value: result})
}
